package com.salelogistics.sale.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

/**
 * @author Hu Kangying
 * @create 2023/7/13-14:07
 * @description 全局唯一自增ID生成器（ID = 时间戳(秒为单位) + redis自增(也就是序列号)）
 */
@Component //注册为spring组件
public class RedisIdWorker {

    /**
     * 初始时间戳，计算出来的结果时间戳 = LocalDateTime.now() - BEGIN_TIMESTAMP
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;

    /**
     * 序列号的位数
     */
    private static final int COUNT_BITS = 32;

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix){
        // 1.生成时间戳
        final LocalDateTime now = LocalDateTime.now();
        final long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        // 时间戳 = 当前时间戳 - 初始时间戳(2022-1-1 0:0:0)
        final long timestamp = nowSecond - BEGIN_TIMESTAMP;
        System.out.println(timestamp + "timestamp");

        // 2.生成序列号
        // 2.1 获取当前日期，精确到天
        /**
         * 由于redis的incr命令，对于单个键最多只能自增到2^64次方值。所以我们这里需要为这个全局ID键添加日期
         * 表示每天都可以自增到2^64次方。这个数字足够大了，一天的订单数无法到达这个数值
         *
         * 日期以冒号(:)隔开的原因是，将来我们可以方便我们统计某年、某月或者某天的一个订单总数
         */
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2 自增长
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        // 3.拼接并返回
        /**
         * timestamp变量属于long类型; 一个long占8个字节，即64个bit位。默认是从低位开始存值的。
         * 将其左移32为表示timestamp变量填充到long字段的前32个bit中，然此时后32bit将会变为0。
         * 此时采用或运算(|)将序列号拼接到后32bit上。至此。完成全局自增唯一ID的拼接
         *
         * 为什么使用或运算拼接？
         * 因为或运算的特性是，有一个为1都为1，都为0才为0; 而此时long字段的后32个bit全部为0，所以，最终值的结果只能看count。
         * count若为1，则拼接后的值也为1，count若为0，则为0。所以，只有使用或运算，才能毫无误差的将count拼接到long字段的后32bit上
         */
        return timestamp << COUNT_BITS | count;
    }

    /*
    public static void main(String[] args) {
        final LocalDateTime time = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
        // toEpochSecond(ZoneOffset) 将指定"日期时间对象"转换为"指定时区"的秒数
        final long second = time.toEpochSecond(ZoneOffset.UTC);
        System.out.println(second);
    }
    */
}
